﻿using System;
using System.Runtime.InteropServices;

namespace Core.Model.Types
{
    [StructLayout(LayoutKind.Sequential)]
    public struct Vector3F : IEquatable<Vector3F>
    {
        public static readonly Vector3F Up = new Vector3F(0.0f, 1f, 0.0f);
        public static readonly Vector3F Down = new Vector3F(0.0f, -1f, 0.0f);
        public static readonly Vector3F Right = new Vector3F(1f, 0.0f, 0.0f);
        public static readonly Vector3F Left = new Vector3F(-1f, 0.0f, 0.0f);
        public static readonly Vector3F Forward = new Vector3F(0.0f, 0.0f, -1f);
        public static readonly Vector3F Backward = new Vector3F(0.0f, 0.0f, 1f);
        public static readonly Vector3F Zero = new Vector3F(0f, 0f, 0f);

        public static Vector3F operator +(Vector3F left, Vector3F right)
        {
            return new Vector3F(left.X + right.X, left.Y + right.Y, left.Z + right.Z);
        }

        public static Vector3F operator -(Vector3F left, Vector3F right)
        {
            return new Vector3F(left.X - right.X, left.Y - right.Y, left.Z - right.Z);
        }

        public static Vector3F Transform(Vector3F position, MatrixF matrix)
        {
            return
                new Vector3F(
                    (float)
                    ((double) position.X*(double) matrix.M11 + (double) position.Y*(double) matrix.M21 +
                     (double) position.Z*(double) matrix.M31) + matrix.M41,
                    (float)
                    ((double) position.X*(double) matrix.M12 + (double) position.Y*(double) matrix.M22 +
                     (double) position.Z*(double) matrix.M32) + matrix.M42,
                    (float)
                    ((double) position.X*(double) matrix.M13 + (double) position.Y*(double) matrix.M23 +
                     (double) position.Z*(double) matrix.M33) + matrix.M43);
        }

        public float Length()
        {
            double num1 = (double)this.Y;
            double num2 = (double)this.X;
            double num3 = (double)this.Z;
            double num4 = num2;
            double num5 = num4 * num4;
            double num6 = num1;
            double num7 = num6 * num6;
            double num8 = num5 + num7;
            double num9 = num3;
            double num10 = num9 * num9;

            return (float)Math.Sqrt(num8 + num10);
        }

        public static float Dot(Vector3F left, Vector3F right)
        {
            return (float)((double)left.Y * (double)right.Y + (double)left.X * (double)right.X + (double)left.Z * (double)right.Z);
        }

        public static Vector3F Cross(Vector3F left, Vector3F right)
        {
            return
                new Vector3F((float) ((double) right.Z*(double) left.Y - (double) left.Z*(double) right.Y),
                             (float) ((double) left.Z*(double) right.X - (double) right.Z*(double) left.X),
                             (float) ((double) right.Y*(double) left.X - (double) left.Y*(double) right.X));
        }

        public Vector3F Normalize()
        {
            float num1 = this.Length();

            if ((double)num1 == 0.0)
            {
                return
                    this;
            }

            float num2 = 1f / num1;

            return new Vector3F(X * num2, Y* num2, Z * num2);
        }

        public Vector3F(float x, float y, float z)
        {
            X = x;
            Y = y;
            Z = z;
        }

        public static int SizeInBytes
        {
            get { return Marshal.SizeOf(typeof(Vector3F)); }
        }

        public readonly float X;
        public readonly float Y;
        public readonly float Z;

        public bool Equals(Vector3F other)
        {
            return other.X.Equals(X) && other.Y.Equals(Y) && other.Z.Equals(Z);
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (obj.GetType() != typeof (Vector3F)) return false;
            return Equals((Vector3F) obj);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                int result = X.GetHashCode();
                result = (result*397) ^ Y.GetHashCode();
                result = (result*397) ^ Z.GetHashCode();
                return result;
            }
        }

        public static bool operator ==(Vector3F left, Vector3F right)
        {
            return left.Equals(right);
        }

        public static bool operator !=(Vector3F left, Vector3F right)
        {
            return !left.Equals(right);
        }

        public static Vector3F operator *(float scale, Vector3F vector)
        {
            return vector * scale;
        }

        public static Vector3F operator *(Vector3F vector, float scale)
        {
            return new Vector3F(vector.X*scale, vector.Y*scale, vector.Z*scale);
        }

        public static Vector3F operator /(Vector3F vector, float scale)
        {
            return new Vector3F(vector.X/scale, vector.Y/scale, vector.Z/scale);
        }

        public static float DistanceSquared(float x1, float z1, float x2, float z2)
        {
            return
                ((x2 - x1)*(x2 - x1)) + ((z2 - z1)*(z2 - z1));
        }
    }
}